#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "tetris_game.h"

#define CONTENT_PATH "data"
#ifdef __APPLE__
#define CONTENT_PATH "tetryx.app/Contents/Resources"
#endif

Game_particle		particles[MAX_PARTICLES];

// images
static GL_image		logo;
static GL_image		star;
static GL_image 	menu_images[3];
static GL_image 	block_images[9];

static GL_image 	block_particles[7][3];

static GL_font2		mainFont2;
GL_label		new_game_label, options_label, exit_label, ivan_label;
GL_label		options_resolution_label, options_fullscreen_label, options_back_label;
GL_label		options_resolution_value, options_fullscreen_value;

// some other crap
int 			options_res_number, options_fullscreen_flag, options_menu_position, menu_position = 0;
int 			block_array[10][18];


// tetrimino shapes
int			shape_sizes[7][2] = {
				{1,4},
				{2,2},
				{2,3},
				{2,3},
				{2,3},
				{2,3},
				{3,2}};

int			shapes_array[7][4][4] = {
				{{5,0,0,0},
				 {5,0,0,0},
				 {5,0,0,0},
				 {5,0,0,0}},

				{{8,8,0,0},
				 {8,8,0,0},
				 {0,0,0,0},
				 {0,0,0,0}},

				{{4,4,0,0},
				 {4,0,0,0},
				 {4,0,0,0},
				 {0,0,0,0}},
	
				{{6,6,0,0},
				 {0,6,0,0},
				 {0,6,0,0},
				 {0,0,0,0}},

				{{7,0,0,0},
				 {7,7,0,0},
				 {0,7,0,0},
				 {0,0,0,0}},

				{{0,2,0,0},
				 {2,2,0,0},
				 {2,0,0,0},
				 {0,0,0,0}},
	
				{{0,3,0,0},
				 {3,3,3,0},
				 {0,0,0,0},
				 {0,0,0,0}}
				};

// timer
static long			last_ticks;
float				elapsed;

// block dropping
Game_playerBlock		playerBlock;
int				nextBlock;
float 				dropSpeed = 0.6f;
float				lastDrop = 0;

// fader
int				fadeStatus = 0;
float				fadeValue = 0.0f;
float				fadeSpeed = 5.0f;
int				nextState;

void Init_Particles() {
	int i;
	float max = RAND_MAX;
	srand(time(NULL));
	for(i=0; i <= MAX_PARTICLES; i++) {
		resetParticle(&particles[i]);
	}
}

void Game_MenuMove(int direction) {
	switch(gameState) {
		case STATE_MENU:
			if(direction == MENU_UP) {
				if(menu_position > 0)
					menu_position--;
			} else {
				if(menu_position < 2)
					menu_position++;
			}
		break;
		case STATE_OPTIONS:
			switch(direction) {
				case MENU_UP:
					if(options_menu_position > 0)
						options_menu_position--;
					break;
				case MENU_DOWN:
					if(options_menu_position < 2)
						options_menu_position++;
					break;
				case MENU_LEFT:
					switch(options_menu_position){
						case 0:
							if(options_res_number > 0)
								options_res_number--;
							char resolution[32] = "";
							sprintf(resolution, "%dx%d", GL_GetModeWidth(options_res_number), GL_GetModeHeight(options_res_number));
							GL_SetLabelText(&options_resolution_value, resolution);
							break;
						case 1:
							if(options_fullscreen_flag == 0) {
								options_fullscreen_flag = 1;
								GL_SetLabelText(&options_fullscreen_value, "Yes");
							} else {
								options_fullscreen_flag = 0;
								GL_SetLabelText(&options_fullscreen_value, "No");
							}
							break;
						default:
							break;
					}
					break;
				case MENU_RIGHT:
					switch(options_menu_position){
						case 0:
							if(options_res_number < GL_GetNumVideoModes()-1)
								options_res_number++;
							char resolution[32] = "";
							sprintf(resolution, "%dx%d", GL_GetModeWidth(options_res_number), GL_GetModeHeight(options_res_number));
							GL_SetLabelText(&options_resolution_value, resolution);
							break;
						case 1:
							if(options_fullscreen_flag == 0) {
								options_fullscreen_flag = 1;
								GL_SetLabelText(&options_fullscreen_value, "Yes");
							} else {
								options_fullscreen_flag = 0;
								GL_SetLabelText(&options_fullscreen_value, "No");
							}
							break;
						default:
							break;
					}
					break;
				default:
					break;
			}
		break;
		case STATE_GAME:
		break;
	}
}

void Game_FadeAndChangeState(int changeState) {
	nextState = changeState;
	fadeStatus = 1;
}

void Game_MenuOK() {

	if(gameState == STATE_MENU) {
		switch(menu_position) {
			case 0:
				Game_FadeAndChangeState(STATE_GAME);
				Game_Start();
				break;
			case 1:
				Game_FadeAndChangeState(STATE_OPTIONS);
				break;
			case 2:
				exitFlag = 1;
				break;
			default:
				break;
		}
	} else if(gameState == STATE_OPTIONS) {
		switch(options_menu_position) {
			case 2:
				GL_SetVideoMode(options_res_number, options_fullscreen_flag);
				Game_FadeAndChangeState(STATE_MENU);
			break;
			default:
				break;
		}
	}
}


void Game_MoveBlock(int moveCode)
{
	switch(moveCode) {
		case MOVE_LEFT:
			if(blockCollision(playerBlock.x, playerBlock.y, MOVE_LEFT) == 0)
				playerBlock.x--;
			break;
		case MOVE_RIGHT:
			if(blockCollision(playerBlock.x, playerBlock.y, MOVE_RIGHT) == 0)
				playerBlock.x++;
		break;
		case MOVE_DOWN:
			Game_MoveDown(1);
			break;
	}
}

void Game_DropBlock() {
	Game_MoveDown(playerBlock.dropOffset+1);
}

void Game_RotateBlock() {
	int i,j;
	byte newArray[4][4] =	{{0,0,0,0},
				 {0,0,0,0},
				 {0,0,0,0},
				 {0,0,0,0}};
	byte oldArray[4][4] =	{{0,0,0,0},
				 {0,0,0,0},
				 {0,0,0,0},
				 {0,0,0,0}};
	
	for(j = 0; j < 4; j++) {
		for(i = 0; i < 4; i++) {
			oldArray[i][j] = playerBlock.shape[i][j];
		}
	}
	
	for(j = 0; j < 4; j++) {
		for(i = 0; i < 4; i++) {
			newArray[3-i][j] = playerBlock.shape[j][i];
			playerBlock.shape[j][i] = 0;
		}
	}

	for(j = 0; j < playerBlock.width; j++) {
		for(i = 0; i < 4; i++) {
			playerBlock.shape[playerBlock.width-j-1][i] = newArray[3-j][i];
		}
	}

	int tmp = playerBlock.height;
	playerBlock.height = playerBlock.width;
	playerBlock.width = tmp;

	if(blockCollision(playerBlock.x, playerBlock.y, MOVE_NONE) > 0) {
		for(j = 0; j < 4; j++) {
			for(i = 0; i < 4; i++) {
				playerBlock.shape[i][j] = oldArray[i][j];
			}
		}
		tmp = playerBlock.height;
		playerBlock.height = playerBlock.width;
		playerBlock.width = tmp;
	}
	
	
}

int Game_Init() {
	GL_LoadImage(CONTENT_PATH"/logo2.png", &logo);
	GL_LoadImage(CONTENT_PATH"/star.png", &star);
	
	GL_LoadImage(CONTENT_PATH"/red1.png", &block_particles[0][0]);
	GL_LoadImage(CONTENT_PATH"/red2.png", &block_particles[0][1]);
	GL_LoadImage(CONTENT_PATH"/red3.png", &block_particles[0][2]);
	
	GL_LoadImage(CONTENT_PATH"/green1.png", &block_particles[1][0]);
	GL_LoadImage(CONTENT_PATH"/green2.png", &block_particles[1][1]);
	GL_LoadImage(CONTENT_PATH"/green3.png", &block_particles[1][2]);
	
	GL_LoadImage(CONTENT_PATH"/blue1.png", &block_particles[2][0]);
	GL_LoadImage(CONTENT_PATH"/blue2.png", &block_particles[2][1]);
	GL_LoadImage(CONTENT_PATH"/blue3.png", &block_particles[2][2]);

	GL_LoadImage(CONTENT_PATH"/cyan1.png", &block_particles[3][0]);
	GL_LoadImage(CONTENT_PATH"/cyan2.png", &block_particles[3][1]);
	GL_LoadImage(CONTENT_PATH"/cyan3.png", &block_particles[3][2]);

	GL_LoadImage(CONTENT_PATH"/yellow1.png", &block_particles[4][0]);
	GL_LoadImage(CONTENT_PATH"/yellow2.png", &block_particles[4][1]);
	GL_LoadImage(CONTENT_PATH"/yellow3.png", &block_particles[4][2]);

	GL_LoadImage(CONTENT_PATH"/orange2.png", &block_particles[5][0]);
	GL_LoadImage(CONTENT_PATH"/orange2.png", &block_particles[5][1]);
	GL_LoadImage(CONTENT_PATH"/orange3.png", &block_particles[5][2]);

	GL_LoadImage(CONTENT_PATH"/purple1.png", &block_particles[6][0]);
	GL_LoadImage(CONTENT_PATH"/purple2.png", &block_particles[6][1]);
	GL_LoadImage(CONTENT_PATH"/purple3.png", &block_particles[6][2]);

	GL_LoadImage(CONTENT_PATH"/newgame.png", &menu_images[0]);
	GL_LoadImage(CONTENT_PATH"/options.png", &menu_images[1]);
	GL_LoadImage(CONTENT_PATH"/exit.png", &menu_images[2]);

	GL_LoadImage(CONTENT_PATH"/red.png", &block_images[BLOCK_RED]);
	GL_LoadImage(CONTENT_PATH"/yellow.png", &block_images[BLOCK_YELLOW]);
	GL_LoadImage(CONTENT_PATH"/purple.png", &block_images[BLOCK_PURPLE]);
	GL_LoadImage(CONTENT_PATH"/blue.png", &block_images[BLOCK_BLUE]);
	GL_LoadImage(CONTENT_PATH"/cyan.png", &block_images[BLOCK_CYAN]);
	GL_LoadImage(CONTENT_PATH"/orange.png", &block_images[BLOCK_ORANGE]);
	GL_LoadImage(CONTENT_PATH"/green.png", &block_images[BLOCK_GREEN]);

	GL_CreateFont2(CONTENT_PATH"/Foster.PFB", &mainFont2);
	GL_color_RGBA c1 = {255,255,255,255};
	
	GL_CreateLabel(&new_game_label, &mainFont2, "new game", 256, 48, c1);
	GL_CreateLabel(&options_label, &mainFont2, "options", 256, 48, c1);
	GL_CreateLabel(&exit_label, &mainFont2, "exit", 256, 48, c1);

	GL_CreateLabel(&options_resolution_label, &mainFont2, "Resolution:", 128, 24, c1);
	GL_CreateLabel(&options_fullscreen_label, &mainFont2, "Fullscreen:", 128, 24, c1);
	GL_CreateLabel(&options_back_label, &mainFont2, "Apply", 128, 24, c1);
	
	char resolution[32] = "";
        sprintf(resolution, "%dx%d", GL_GetModeWidth(0), GL_GetModeHeight(0));
	GL_CreateLabel(&options_resolution_value, &mainFont2, resolution, 256, 24, c1);
	GL_CreateLabel(&options_fullscreen_value, &mainFont2, "No", 128, 24, c1);
	
	GL_color_RGBA c2 = {57,87,118,255};
	GL_CreateLabel(&ivan_label, &mainFont2, "Ivan Safrin", 256, 38, c2);
	
	Init_Particles();

	int i,j;
	for(i = 0; i < 10; i++) {
		for(j = 0; j < 18; j++) {
			block_array[i][j] = BLOCK_EMPTY;
		}
	}

	return 1;
}

void Game_DrawOptions() {
	GL_color_RGBA c1 = {0,0,0,100};
	GL_color_RGBA c2 = {29,45,60,175};
	GL_color_RGBA c3 = {0,0,0,0};
	GL_color_RGBA c4 = {0,0,0,125};
	GL_color_RGBA c5 = {0,0,0,45};
	
	GL_Rect(0.0f,0.0f,0.4f,1.0f, c1, 0);
	
	GL_VerticalGradientRect(0.3995f,0.0f,0.006f,1.0f, c4, c3);
	GL_VerticalGradientRect(0.2f,0.0f,0.2f,1.0f, c3, c5);
	
	GL_Rect(0.0f,0.25f + ((float)options_menu_position * 0.03f),0.4f,0.03f, c2, 0);
	GL_DrawLabel(&options_resolution_label, 0.12f, 0.01f,0.25f);
	GL_DrawLabel(&options_resolution_value, 0.24f, 0.25f,0.25f);
	GL_DrawLabel(&options_fullscreen_label, 0.12f, 0.01f,0.28f);
	GL_DrawLabel(&options_fullscreen_value, 0.12f, 0.25f,0.28f);
	GL_DrawLabel(&options_back_label, 0.12f, 0.01f,0.31f);
}

void Game_DrawMenu() {
	GL_color_RGBA c1 = {0,0,0,100};
	GL_color_RGBA c2 = {29,45,60,175};
	GL_color_RGBA c3 = {0,0,0,0};
	GL_color_RGBA c4 = {0,0,0,125};
	GL_color_RGBA c5 = {0,0,0,45};
	
	GL_Rect(0.0f,0.0f,0.25f,1.0f, c1, 0);
	
	GL_VerticalGradientRect(0.2495f,0.0f,0.006f,1.0f, c4, c3);
	GL_VerticalGradientRect(0.1f,0.0f,0.15f,1.0f, c3, c5);
	
	GL_Rect(0.0f,0.25f + ((float)menu_position * 0.04f),0.25f,0.04f, c2, 0);
	int i;
	/*
	for(i=0; i<3; i++) {
		GL_DrawImage(&menu_images[i], 0.3f, 0.3f + ((float)i*0.12f), 0.4f, 0.12f, 0.0f);
	}
	*/
	
	GL_DrawLabel(&new_game_label, 0.18f, 0.09f,0.245f);
	GL_DrawLabel(&options_label, 0.18f, 0.128f,0.285f);
	GL_DrawLabel(&exit_label, 0.18f, 0.183f,0.325f);
	GL_DrawLabel(&ivan_label, 0.2f, 0.86f,0.71f);
	//GL_DrawImage(&logo, 0.3f, 0.03f, 0.4f, 0.20f, 0.0f);
}

void Game_DrawFade() {

	if(fadeStatus == 1) {
		fadeValue += fadeSpeed*(elapsed*100.0f);
		if(fadeValue > 255) {
			fadeValue = 255;
			fadeStatus = 2;
			gameState = nextState;
		}
	}
	if(fadeStatus == 2) {
		fadeValue -= fadeSpeed*(elapsed*100.0f);
		if(fadeValue < 0) {
			fadeValue = 0;
			fadeStatus = 0;
		}

	}
	
	GL_color_RGBA c1 = {0,0,0,fadeValue};
	GL_Rect(0.0f,0.0f,1.0f,1.0f, c1, 0.0f);
}


byte blockCollision(byte x, byte y, byte moveCode) {
	byte i,j;

	switch(moveCode) {
		case MOVE_NONE:
			if(x+playerBlock.width > 10 || y+playerBlock.height > 17)
				return 1;

			for(i = 0; i < 4; i++) {
				for(j = 0; j < 4; j++) {
					if(playerBlock.shape[j][i] != BLOCK_EMPTY && block_array[x+i][y+j] != BLOCK_EMPTY) 
						return 1;
				}
			}
			break;
		case MOVE_LEFT:
			if(x == 0) {
				return 1;
			}
			for(i = 0; i < 4; i++) {
				for(j = 0; j < 4; j++) {
					if(playerBlock.shape[j][i] != BLOCK_EMPTY && block_array[x+i-1][y+j] != BLOCK_EMPTY)
						return 1;
				}
			}
			break;
		case MOVE_RIGHT:
			if(x == 10-playerBlock.width) {
				return 1;
			}
			for(i = 0; i < 4; i++) {
				for(j = 0; j < 4; j++) {
					if(playerBlock.shape[j][i] != BLOCK_EMPTY && block_array[x+i+1][y+j] != BLOCK_EMPTY)
						return 1;
				}
			}
			break;
		case MOVE_DOWN:
			if(y+1 == 19-playerBlock.height) {
				return 1;
			}
			for(i = 0; i < 4; i++) {
				for(j = 0; j < 4; j++) {
					if(playerBlock.shape[j][i] != BLOCK_EMPTY && block_array[x+i][y+j+1] != BLOCK_EMPTY)
						return 1;
				}
			}
			break;
	}
	return 0;
}

void Game_CheckCompleted() {
	byte i,j,h,complete;
	for(j = 0; j < 18; j++) {
		complete = 0;
		for(i = 0; i < 10; i++) {
			if(block_array[i][j] != BLOCK_EMPTY) {
				complete++;
			}
		}
		if(complete == 10) {
			for(h = j; h > 0; h--) {
				for(i = 0; i < 10; i++) {
					block_array[i][h] = block_array[i][h-1];
				}
			}
		}
	}
}

void Game_MoveDown(byte moveOffset) {
	int i,j;

	// if there's a collision, copy player block to array
	// and if not, just move it down
	if(blockCollision(playerBlock.x, playerBlock.y+moveOffset-1, MOVE_DOWN) > 0) {
		for(i = 0; i < 4; i++) {
			for(j = 0; j < 4; j++) {
				if(playerBlock.shape[j][i] != BLOCK_EMPTY)
					block_array[playerBlock.x+i][playerBlock.y+moveOffset+j-1] = playerBlock.shape[j][i];
			}
		}
		Game_CheckCompleted();
		Game_NewBlock();
	} else {
		playerBlock.y += moveOffset;
	}
}

byte getBlockOffset() {
	int newOffset = 0;
	while(blockCollision(playerBlock.x, playerBlock.y+newOffset, MOVE_DOWN) == 0 && newOffset < 18) {
		newOffset++;
	}
	return newOffset;
}


void Game_DrawGameScreen() {
	GL_color_RGBA c2 = {0,0,0,200};
	GL_Rect(0.2f,0.01f, 0.4f, 0.73f, c2, 0);
	GL_Rect(0.02f,0.02f, 0.16f, 0.16f, c2, 0);

	byte linesToKill[4] = {255,255,255,255};
	byte linesKilled = 0;
	// render the tetris blocks 
	byte i,j;
	for(j = 0; j < 18; j++) {
		for(i = 0; i < 10; i++) {
			if(block_array[i][j] != BLOCK_EMPTY) {
				GL_DrawImage(&block_images[block_array[i][j]], 0.2f+((float)i*0.04f), 0.02f+((float)j*0.04f), 0.04f, 0.04f, 0.0f);
			}
		}
	}
	
	lastDrop += elapsed;
	if(lastDrop >= dropSpeed) {
		lastDrop = 0;
		Game_MoveDown(1);
	}
	

	GL_color_RGBA a1 = {255,255,255,80};
	playerBlock.dropOffset = getBlockOffset();
	// render the falling block, the drop reflection and the next block in one loop
	for(i = 0; i < 4; i++) {
		for(j = 0; j < 4; j++) {
			// draw offset
			if(playerBlock.shape[j][i] != BLOCK_EMPTY && playerBlock.dropOffset != 0) {
				GL_DrawTintedImage(&block_images[playerBlock.shape[j][i]], ((float)playerBlock.x*0.04f)+0.2f+((float)i*0.04f), ((float)(playerBlock.y+playerBlock.dropOffset)*0.04f)+0.02f+((float)j*0.04f), 0.04f, 0.04f, 0.0f, a1);
			}
			// draw player
			if(playerBlock.shape[j][i] != BLOCK_EMPTY) {
				GL_DrawImage(&block_images[playerBlock.shape[j][i]], ((float)playerBlock.x*0.04f)+0.2f+((float)i*0.04f), ((float)playerBlock.y*0.04f)+0.02f+((float)j*0.04f), 0.04f, 0.04f, 0.0f);
			}
			// draw next
			if(shapes_array[nextBlock][j][i] != BLOCK_EMPTY) {
				GL_DrawImage(&block_images[shapes_array[nextBlock][j][i]], 0.02f+((float)i*0.04f), 0.02f+((float)j*0.04f), 0.04f, 0.04f, 0.0f);
			}
		}
	}
}


void Game_DrawBackground() {
	GL_color_RGBA c2 = {255,255,255,255};
	GL_color_RGBA c1 = {77,119,162,255};
	GL_GradientRect(0,0,1,1, c1, c2);
	int i;
	for(i=0; i < MAX_PARTICLES; i++) {
		renderParticle(&particles[i]);
	}
}

void resetParticle(Game_particle *p) {
	int type1 = rand() % 7;
	int type2 = rand() % 3;
	float size = 0.2/ ( ((float)type2+1)*1.5);
	float speed = 0.0005/((float)type2+1) + ((rand()/(float)RAND_MAX)*0.003f);

	Game_particle new_particle = {rand()/(float)RAND_MAX, -0.15f,0, rand()/(float)RAND_MAX, size, speed, type1, type2, (rand()/(float)RAND_MAX) * 0.001}; 
	*p = new_particle;
}

void renderParticle(Game_particle *p) {
	
	//GL_DrawImage(&star, p->x, p->y, p->size, p->size, p->rotation);
	GL_DrawZImage(&block_particles[p->type1][p->type2], p->x, p->y, p->size, p->size, p->rotation, 0.01*(p->type2+1));
	
	p->y += p->speed*(elapsed*100.0f);
	p->rotation += p->rotation_speed*(elapsed*100.0f);

	p->speed += elapsed * p->accel; //0.0015f;
	
	if(p->rotation > 360) 
		p->rotation = 0;
	if(p->y > 1.1) {
		resetParticle(p);
	}
}

void Game_NewBlock() {
	
	// copy the new block into the player array
	// we could just render the blocks from the
	// reference array, but for rotations this
	// is way faster.
	
	int i,j;
	for(i = 0; i < 4; i++) {
		for(j = 0; j < 4; j++) {
			playerBlock.shape[j][i] = shapes_array[nextBlock][j][i];
		}
	}
	playerBlock.width = shape_sizes[nextBlock][0];
	playerBlock.height = shape_sizes[nextBlock][1];

	playerBlock.x = 4;
	playerBlock.y = 0;
	
	nextBlock = rand() % 7;

	if(blockCollision(playerBlock.x, playerBlock.y, MOVE_NONE) > 0) {
		Game_FadeAndChangeState(STATE_MENU);
	}
}

void Game_Start() {
	int i,j;
	for(i = 0; i < 10; i++) {
		for(j = 0; j < 18; j++) {
			block_array[i][j] = BLOCK_EMPTY;
		}
	}
	nextBlock = rand() % 7;
	Game_NewBlock();
}

void Game_Render() {
	long ticks = Timer_GetTicks();
  	elapsed = (float)((double)(ticks-last_ticks)/1000.0);
	last_ticks = ticks;
	GL_BeginRender();
	Game_DrawBackground();
	switch(gameState) {
		case STATE_MENU:
			Game_DrawMenu();
		break;
		case STATE_OPTIONS:
			Game_DrawOptions();
		break;
		case STATE_GAME:
			Game_DrawGameScreen();
		break;
	}
	if(fadeStatus != 0) {
		Game_DrawFade();
	}
	GL_EndRender();
}
